/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

/*! @file test/Misc.cc
    @brief Class Misc definition.
    @author @ref Guillaume_Terrissol
    @date 15th February 2010 - 21st January 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <cppunit/extensions/HelperMacros.h>

#include <stdexcept>

#include <BundleMgr.hh>
#include <Exception.hh>
#include <Utils.hh>

#include "Utils.hh"

namespace CHK
{
//------------------------------------------------------------------------------
//                                  Test Class
//------------------------------------------------------------------------------

    /*! @brief Miscellaneous tests.
        @version 1.0
     */
    class Misc : public CppUnit::TestFixture
    {
#ifndef NOT_FOR_DOXYGEN
        // Tests registering
        CPPUNIT_TEST_SUITE(Misc);
        CPPUNIT_TEST(testingContextLogs);
        CPPUNIT_TEST(testingProperties);
        CPPUNIT_TEST(testingEvents);
        CPPUNIT_TEST(testingPaths);
        CPPUNIT_TEST_SUITE_END();
#endif  // NOT_FOR_DOXYGEN
    public:
        //! @name "Interface"
        //@{
virtual         ~Misc() = default;      //!< Destructor.
        //@}

    private:
        //! @name Testing
        //@{
        void    testingContextLogs();   //!< ... the context.
        void    testingProperties();    //!< ... properties
        void    testingEvents();        //!< ... events.
        void    testingPaths();         //!< ... paths.
        //@}
    };


    CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(Misc, "Miscellaneous tests");


//------------------------------------------------------------------------------
//                                     Tests
//------------------------------------------------------------------------------

    /*! @test @par Checking every possible output for the logs.
        Both standard, and error logs are checked.@n
        Outputs are, in that order :
        - standard streams,
        - internal strings,
        - external files.
     */
    void Misc::testingContextLogs()
    {
        const std::string   kOut    = "Hello world.\n"
                                      "How do you do ?\n";
        const std::string   kErr    = "Goodbye world.\n"
                                      "See you next time !\n";

        {
            {
                RedirectStream  lHideOutput{"stdout.txt", stdout};
                RedirectStream  lHideError{"stderr.txt", stderr};

                auto    lMgr = OSGi::BundleMgr::make({ "cleanCache" });

                lMgr->context()->out() << kOut;
                lMgr->context()->err() << kErr;
            }

            CPPUNIT_ASSERT_MESSAGE("Output log as cout failed",
                                   dynamic_cast<std::ostringstream&>(std::ostringstream{}
                                                                            << std::fstream{"stdout.txt",
                                                                                            std::fstream::in | std::fstream::binary}.rdbuf()
                                                                     ).str() == kOut);
            CPPUNIT_ASSERT_MESSAGE("Error log as cerr failed",
                                   dynamic_cast<std::ostringstream&>(std::ostringstream{}
                                                                            << std::fstream{"stderr.txt",
                                                                                            std::fstream::in | std::fstream::binary}.rdbuf()
                                                                    ).str() == kErr);
        }
        {
            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });

            lMgr->context()->out() << kOut;
            lMgr->context()->err() << kErr;

            CPPUNIT_ASSERT_MESSAGE("Output log as string failed",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() == kOut);

            CPPUNIT_ASSERT_MESSAGE("Error log as string failed",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str() == kErr);
        }
        {
            std::string lTmpDir{};
            {
                auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=stdout.txt", "err=stderr.txt" });

                lTmpDir = lMgr->context()->temporaryPath();

                lMgr->context()->out() << kOut;
                lMgr->context()->err() << kErr;
            }
            CPPUNIT_ASSERT_MESSAGE("Output log as file failed",
                                   dynamic_cast<std::ostringstream&>(std::ostringstream{}
                                                                            << std::fstream{lTmpDir + "/stdout.txt",
                                                                                            std::fstream::in | std::fstream::binary}.rdbuf()
                                                                     ).str() == kOut);
            CPPUNIT_ASSERT_MESSAGE("Error log as file failed",
                                   dynamic_cast<std::ostringstream&>(std::ostringstream{}
                                                                            << std::fstream{lTmpDir + "/stderr.txt",
                                                                                            std::fstream::in | std::fstream::binary}.rdbuf()
                                                                    ).str() == kErr);
        }
    }


    /*! @test @par Checking properties management.
        The default properties (in the configuration file) and checked.@n
        Every type of properties is both written and read back from the context (boolean, integer, float, string).@n
        A missing property is accessed, throwing an exception.
     */
    void Misc::testingProperties()
    {
        auto    lMgr    = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        auto*   lProps  = lMgr->context()->properties();
        CPPUNIT_ASSERT_MESSAGE("Bundle manager : no properties", lProps != nullptr);

        CPPUNIT_ASSERT_MESSAGE("Missing property : comment", lProps->has("application.comment"));
        CPPUNIT_ASSERT_MESSAGE("Invalid property value : comment", lProps->get("application.comment") == "A simple comment");

        lProps->set("application.comment", "A modified comment");
        CPPUNIT_ASSERT_MESSAGE("Invalid property value : comment", lProps->get("application.comment") == "A modified comment");

        lProps->set("application.bool", true);
        CPPUNIT_ASSERT_MESSAGE("Invalid property value : bool", lProps->getBool("application.bool") == true);
        lProps->set("application.bool", false);
        CPPUNIT_ASSERT_MESSAGE("Invalid property value : bool", lProps->getBool("application.bool") == false);

        lProps->set("application.integer", 2010);
        CPPUNIT_ASSERT_MESSAGE("Invalid property value : integer", lProps->getInteger("application.integer") == 2010);

        lProps->set("application.float", 3.141592653);
        CPPUNIT_ASSERT_MESSAGE("Invalid property value : float", lProps->getFloat("application.float") == 3.141592653);

        try
        {
            lProps->get("application.Float");
        }
        catch(OSGi::Exception& pE)
        {
            CPPUNIT_ASSERT_MESSAGE("Unexpected exception",
                                    std::string(pE.what()) == "application.Float not found");
        }

        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str().empty());
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    namespace
    {
        class Sender
        {
        public:
            OSGI_DECL_SIGNAL(fooVoid)
            OSGI_DECL_SIGNAL(fooInt, int)
            OSGI_DECL_SIGNAL(fooMany, const std::string&, bool)
        };

        class Receiver
        {
        public:
            void callVoid()
            {
                std::cout << "callVoid()" << std::endl;
            }
            void callInt(int pI)
            {
                std::cout << "callInt(" << pI << ")" << std::endl;
            }
            void callMany(const std::string& pS, bool pB)
            {
                std::cout << "callMany(" << pS << ", " << (pB ? "true" : "false") << ")" << std::endl;
            }
        };
    }


    /*! @test @par Checking the signal/slot mechanism.
        Signals with none, one, and two parameters (different types) are connected to matching slots, and emitted.
     */
    void Misc::testingEvents()
    {
        {
            RedirectStream  lHideOutput{"stdout.txt", stdout};
            RedirectStream  lHideError{"stderr.txt", stderr};
            auto            lSender     = std::make_shared<Sender>();
            auto            lReceiver1  = std::make_shared<Receiver>();
            {
                auto        lReceiver2  = std::make_shared<Receiver>();

                OSGI_CONNECT(lSender, OSGI_SIGNAL(fooVoid), lReceiver1, OSGI_SLOT(callVoid))
                OSGI_CONNECT(lSender, OSGI_SIGNAL(fooInt),  lReceiver2, OSGI_SLOT(callInt))
                OSGI_CONNECT(lSender, OSGI_SIGNAL(fooMany), lReceiver2, OSGI_SLOT(callMany))

                OSGI_EMIT lSender->fooVoid();
                OSGI_EMIT lSender->fooInt(2010);
                OSGI_EMIT lSender->fooMany(std::string{"OSGi"}, true);

                OSGI_DISCONNECT(lSender, OSGI_SIGNAL(fooInt), lReceiver2, OSGI_SLOT(callInt))

                OSGI_EMIT lSender->fooVoid();
                OSGI_EMIT lSender->fooInt(2010);
                OSGI_EMIT lSender->fooMany(std::string{"OSGi"}, true);
            }

            OSGI_EMIT lSender->fooVoid();
            OSGI_EMIT lSender->fooInt(2010);
            OSGI_EMIT lSender->fooMany(std::string{"OSGi"}, true);
        }

        CPPUNIT_ASSERT_MESSAGE("Output log as cout failed",
                               dynamic_cast<std::ostringstream&>(std::ostringstream{}
                                                                        << std::fstream{"stdout.txt",
                                                                                        std::fstream::in | std::fstream::binary}.rdbuf()
                                                                 ).str() ==
                               "callVoid()\n"
                               "callInt(2010)\n"
                               "callMany(OSGi, true)\n"
                               "callVoid()\n"
                               "callMany(OSGi, true)\n"
                               "callVoid()\n");
        CPPUNIT_ASSERT_MESSAGE("Error log as cerr failed",
                               dynamic_cast<std::ostringstream&>(std::ostringstream{}
                                                                        << std::fstream{"stderr.txt",
                                                                                        std::fstream::in | std::fstream::binary}.rdbuf()
                                                                ).str().empty());
    }


    /*! @test @par Checking Path handling functions.
        - Absolute and relative paths,
        - Working directory,
        - Invalid path.
     */
    void Misc::testingPaths()
    {
        std::string lWD = OSGi::workingDirectory();

        // Absolute and relative paths.
        CPPUNIT_ASSERT_MESSAGE("Absolute path erroneously considered relative",
#if defined(_WIN32)
                               OSGi::isPathAbsolute("c:/mingw"));
#else   // defined(_WIN32)
                               OSGi::isPathAbsolute("/usr"));
#endif  // defined(_WIN32)
        CPPUNIT_ASSERT_MESSAGE("Weird absolute path erroneously considered relative",
#if defined(_WIN32)
                               OSGi::isPathAbsolute("c://mingw")
#else   // defined(_WIN32)
                               OSGi::isPathAbsolute("//usr")
#endif  // defined(_WIN32)
        );

        CPPUNIT_ASSERT_MESSAGE("Relative path erroneously considered absolute",
#if defined(_WIN32)
                               !OSGi::isPathAbsolute("c:mingw/")
#else   // defined(_WIN32)
                               !OSGi::isPathAbsolute("usr/")
#endif  // defined(_WIN32)
        );

        // Working directory is absolute.
        CPPUNIT_ASSERT_MESSAGE("Invalid working directory",
#if defined(_WIN32)
                                (3 <= lWD.length()) && std::isalpha(lWD[0]) && (lWD.substr(1, 2) == ":/")
#else   // defined(_WIN32)
                                (1 <= lWD.length()) && (lWD[0] == '/')
#endif  // defined(_WIN32)
        );

        // Valid case.
        try
        {
            CPPUNIT_ASSERT_MESSAGE("Relative path extraction failed",
                                   OSGi::fromWorkingDirectory(lWD + "tmp/check") == "tmp/check");

        }
        catch(std::exception&)
        {
            CPPUNIT_ASSERT_MESSAGE("Unexepected exception while checking nominal relative path extraction", false);
        }
        // Path too short.
        std::string lTooShort = lWD.substr(lWD.length() - 1);
        try
        {
            OSGi::fromWorkingDirectory(lTooShort);
        }
        catch(std::exception& pEx)
        {
            CPPUNIT_ASSERT_MESSAGE("Unexpected exception while checking too short path",
                                   typeid(pEx) == typeid(std::invalid_argument) &&
                                   pEx.what() == lTooShort + " doesn't start with " + lWD);
        }
        // Path not from Working directory
        std::string lBadPath = lWD;
        lBadPath[lBadPath.length() / 2] += 1;
        try
        {
            OSGi::fromWorkingDirectory(lBadPath);
        }
        catch(std::exception& pEx)
        {
            CPPUNIT_ASSERT_MESSAGE("Unexpected exception while checking too short path",
                                   typeid(pEx) == typeid(std::invalid_argument) &&
                                   pEx.what() == lBadPath + " doesn't start with " + lWD);
        }
    }
}
